﻿#region Copyright

// // The contents of this file are subject to the Mozilla Public License
// // Version 1.1 (the "License"); you may not use this file except in compliance
// // with the License. You may obtain a copy of the License at
// //   
// // http://www.mozilla.org/MPL/
// //   
// // Software distributed under the License is distributed on an "AS IS"
// // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// // License for the specific language governing rights and limitations under 
// // the License.
// //   
// // The Initial Developer of the Original Code is Robert Smyth.
// // Portions created by Robert Smyth are Copyright (C) 2008-2013.
// //   
// // All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using NDependencyInjection.interfaces;
using NoeticTools.NWorkflow.DotNetInterop;
using NoeticTools.NWorkflow.Exceptions;
using NoeticTools.NWorkflow.ProgressMonitoring;
using NoeticTools.NWorkflow.Steps.Gates;


namespace NoeticTools.NWorkflow.Scopes
{
    public class ActionScope<TResult, TStepResult>
    {
        private readonly Action<Action> _addAction;
        private readonly TimeSpan _maximumTimeSpan = TimeSpan.FromMilliseconds(int.MaxValue);
        private readonly IStepRules<TStepResult> _rules;
        private readonly ISystemDefinition _system;
        private readonly IWorkflowRules<TResult> _workflowRules;
        private readonly IWorkflowRunner<TResult> _workflowRunner;

        public ActionScope(IWorkflowRules<TResult> workflowRules, IStepRules<TStepResult> rules, Action<Action> addAction, IWorkflowRunner<TResult> workflowRunner,
                           ISystemDefinition system)
        {
            _workflowRules = workflowRules;
            _rules = rules;
            _addAction = addAction;
            _workflowRunner = workflowRunner;
            _system = system;
        }

        /// <summary>
        ///     Transition to another workflow.
        /// </summary>
        public ThenScope<ActionScope<TResult, TStepResult>> TransitionTo(string destinationStep)
        {
            _addAction(() => _workflowRules.TransitionTo(destinationStep));
            return new ThenScope<ActionScope<TResult, TStepResult>>(this);
        }

        /// <summary>
        ///     Exit from the workflow and the workflow machine with the given result.
        /// </summary>
        public ThenScope<ActionScope<TResult, TStepResult>> Exit(TResult result)
        {
            _addAction(() => _workflowRules.Exit(result));
            return new ThenScope<ActionScope<TResult, TStepResult>>(this);
        }

        /// <summary>
        ///     Start a workflow action.
        /// </summary>
        /// <remarks>
        ///     Runs a workflow action. Use this method to encapsulate a process in an object.
        ///     Allows for synchronous or asynchronous processes.
        /// </remarks>
        public ThenScope<ActionScope<TResult, TStepResult>> Start(IActivity<TStepResult> activity)
        {
            var stepActionRunner = new ActivityRunner<TResult, TStepResult>(activity, _workflowRunner);
            _addAction(stepActionRunner.Start);
            _rules.AddOnAnyEventAction(stepActionRunner.OnEvent);
            _rules.AddOnLeavingAction(stepActionRunner.Cancel);
            return new ThenScope<ActionScope<TResult, TStepResult>>(this);
        }

        public ThenScope<ActionScope<TResult, TStepResult>> StartActivity<T>()
            where T : IActivity<TStepResult>
        {
            return Start(_system.Get<T>());
        }

        public ThenScope<ActionScope<TResult, TStepResult>> StartInnerWorkflow<T>()
            where T : IWorkflowRules<TStepResult>
        {
            return Start(new DelayedActivityBuildActivity<T>(_system));
        }

        private class DelayedActivityBuildActivity<T> : IActivity<TStepResult> where T : IWorkflowRules<TStepResult>
        {
            private readonly ISystemDefinition _system;
            private IWorkflow<TStepResult> _inner;

            public DelayedActivityBuildActivity(ISystemDefinition system)
            {
                _system = system;
                SyncRoot = new object();
            }

            public TStepResult End()
            {
                return _inner.End();
            }

            public object SyncRoot { get; private set; }

            public void Begin(IActivityListener<TStepResult> listener)
            {
                _inner = _system.Get<T>().CreateWorkflow("Name");
                _inner.Begin(listener);
            }

            public void Cancel()
            {
                _inner.Cancel();
            }

            public void OnEvent(object value)
            {
                _inner.OnEvent(value);
            }
        }

        /// <summary>
        ///     Start a sub workflow as an activity.
        /// </summary>
        /// <remarks>
        ///     Creates a workflow from the given rules and runs the workflow as a child activity.
        ///     The current step becomes a composite step running a sub workflow.
        /// </remarks>
        public ThenScope<ActionScope<TResult, TStepResult>> Start(IWorkflowRules<TStepResult> rules)
        {
            return Start(rules.CreateWorkflow(rules.Name, _workflowRunner.TimerFactory));
        }

        public ThenScope<ActionScope<TResult, TStepResult>> StartTimer(TimeSpan timeLimit)
        {
            if (timeLimit > _maximumTimeSpan)
            {
                throw new WorkflowConfigurationException(string.Format("Maximum step time span is {0} days.", (int) _maximumTimeSpan.TotalDays));
            }

            _addAction(() => _workflowRunner.CurrentStep.StartTimer(timeLimit));
            return new ThenScope<ActionScope<TResult, TStepResult>>(this);
        }

        /// <summary>
        ///     Start a sequential run of workflows obtained at runtime from a factory function.
        /// </summary>
        public ThenScope<ActionScope<TResult, TStepResult>> Start(Func<IEnumerable<IWorkflow<Outcome>>> workflowsfactory)
        {
            if (typeof (TStepResult) != typeof (Outcome))
            {
                throw new WorkflowConfigurationException(
                    string.Format("A sequential run of workflows may only be started from a state with a {0} result type", typeof (Outcome).Name));
            }
            return Start((IActivity<TStepResult>) new SequentialWorkflowsRunner((IStepRules<Outcome>) _rules, workflowsfactory));
        }

        /// <summary>
        ///     Invoke the function and set the step's result to the result returned from the function.
        /// </summary>
        public ThenScope<ActionScope<TResult, TStepResult>> InvokeFunction(Func<TStepResult> func)
        {
            var stepSyncAction = new SynchronousFunctionGate<TResult, TStepResult>(func);
            _addAction(() => stepSyncAction.Invoke(_workflowRunner));
            return new ThenScope<ActionScope<TResult, TStepResult>>(this);
        }

        public InvokeThenScope<TResult, TStepResult, ActionScope<TResult, TStepResult>> Invoke(Action action)
        {
            var stepSyncAction = new SynchronousActionGate<TResult>(action);
            _addAction(() => stepSyncAction.Invoke(_workflowRunner));
            return new InvokeThenScope<TResult, TStepResult, ActionScope<TResult, TStepResult>>(_workflowRules, _rules, this, stepSyncAction, _workflowRunner, _system);
        }

        public InvokeThenScope<TResult, TStepResult, ActionScope<TResult, TStepResult>> Invoke<T1>(Action<T1> action, T1 arg)
        {
            var stepSyncAction = new SynchronousActionGate<TResult>(() => action(arg));
            _addAction(() => stepSyncAction.Invoke(_workflowRunner));
            return new InvokeThenScope<TResult, TStepResult, ActionScope<TResult, TStepResult>>(_workflowRules, _rules, this, stepSyncAction, _workflowRunner, _system);
        }

        public InvokeThenScope<TResult, TStepResult, ActionScope<TResult, TStepResult>> Invoke<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2)
        {
            var stepSyncAction = new SynchronousActionGate<TResult>(() => action(arg1, arg2));
            _addAction(() => stepSyncAction.Invoke(_workflowRunner));
            return new InvokeThenScope<TResult, TStepResult, ActionScope<TResult, TStepResult>>(_workflowRules, _rules, this, stepSyncAction, _workflowRunner, _system);
        }

        public ThenScope<ActionScope<TResult, TStepResult>> FireEvent<T>(T value)
        {
            _addAction(() => _workflowRules.OnEventFired(value));
            return new ThenScope<ActionScope<TResult, TStepResult>>(this);
        }

        /// <summary>
        ///     Set this workflow's current result.
        /// </summary>
        /// <remarks>
        ///     Set this workflow's current result without leaving the workflow. Setting the result
        ///     will trigger the testing of OnResult rules which may cause a workflow transition.
        /// </remarks>
        public ThenScope<ActionScope<TResult, TStepResult>> SetResult(TStepResult result)
        {
            _addAction(() => _workflowRules.SetStepResult(result));
            return new ThenScope<ActionScope<TResult, TStepResult>>(this);
        }

        /// <summary>
        ///     Leave workflow with result.
        /// </summary>
        /// <remarks>
        ///     Leave workflow with result. Requires rule for result. If not rule for result found throws exception. This is the same as SetResult but with
        ///     the expectation of leaving the workflow.
        /// </remarks>
        public ThenScope<ActionScope<TResult, TStepResult>> Leave(TStepResult result)
        {
            _addAction(() => _workflowRules.LeaveStep(result));
            return new ThenScope<ActionScope<TResult, TStepResult>>(this);
        }
    }
}